package com.miku.project.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.miku.mikuapicommon.model.entity.User;
import com.miku.project.common.ErrorCode;
import com.miku.project.constant.UserConstant;
import com.miku.project.exception.BusinessException;
import com.miku.project.mapper.UserMapper;
import com.miku.project.model.dto.user.UserEmailBindRequest;
import com.miku.project.model.dto.user.UserEmailLoginRequest;
import com.miku.project.model.dto.user.UserEmailRegisterRequest;
import com.miku.project.model.dto.user.UserRegisterRequest;
import com.miku.project.model.enums.UserAccountStatusEnum;
import com.miku.project.model.vo.UserVO;
import com.miku.project.service.UserService;
import com.miku.project.utils.RedissonLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Random;

import static com.miku.project.constant.EmailConstant.CAPTCHA_CACHE_KEY;
import static com.miku.project.constant.EmailConstant.EMAIL_REGISTER_LOCK;
import static com.miku.project.constant.UserConstant.USER_LOGIN_STATE;
import static com.miku.project.constant.UserConstant.VOCHER;


/**
 * 用户服务实现类
 *
 * @author <a href="https://gitee.com/deng-taotao-eee">miku</a>
 * @from <a href=""></a>
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private RedissonLockUtil redissonLockUtil;

    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "miku";

    /**
     * 用户注册
     *
     * @param userRegisterRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public long userRegister(UserRegisterRequest userRegisterRequest) {
        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String userName = userRegisterRequest.getUserName();
        String checkPassword = userRegisterRequest.getCheckPassword();
        String invitationCode = userRegisterRequest.getInvitationCode();
        //1.校验参数
        if (StringUtils.isAnyEmpty(userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userName.length() > 40) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名过长");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        //  5. 账户不包含特殊字符
        // 匹配由数字、小写字母、大写字母组成的字符串,且字符串的长度至少为1个字符
        String pattern = "[0-9a-zA-Z]+";
        if (!userAccount.matches(pattern)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号包含特殊字符");
        }
        // 6. 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码和校验密码不相同");
        }
        String redissonLock = ("userRegiest" + userAccount).intern();
        //异步执行分布式锁
        return redissonLockUtil.redissonDistributedLocks(redissonLock, () -> {
            //账户不能重复
            if (userMapper.selectCount(Wrappers.<User>lambdaQuery()
                    .eq(User::getUserAccount, userAccount)) > 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户已存在");
            }
            //查询是否是被邀请用户
            User invitedUser = null;
            if (StringUtils.isNotBlank(invitationCode)) {
                invitedUser = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                        .eq(User::getInvitationCode, invitationCode));
                if (invitedUser == null || invitedUser.getIsDelete() == 1) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "邀请码不存在");
                }
            }
            //加密
            String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
            //ak,sk
            String accessKey = DigestUtils.md5DigestAsHex((SALT + userAccount + VOCHER).getBytes());
            String secretKey = DigestUtils.md5DigestAsHex((SALT + userAccount + VOCHER + userPassword).getBytes());

            //3.插入数据
            User user = new User();
            user.setUserAccount(userAccount);
            user.setUserPassword(encryptPassword);
            user.setUserName(userName);
            user.setAccessKey(accessKey);
            user.setSecretKey(secretKey);
            if (invitedUser != null) {
                //是邀请用户,钱包余额默认给100
                user.setBalance(100);
                addWalletBalance(invitedUser.getId(), 100);
            } else {
                //不是邀请用户,钱包余额默认给0
                user.setBalance(30);
            }
            //设置邀请码
            user.setInvitationCode(generateRandomString(8));
            int insert = userMapper.insert(user);
            if (insert == 0) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败");
            }
            return user.getId();
        }, "注册账户失败");
    }

    @Override
    public UserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
        }

        //  5. 账户不包含特殊字符
        // 匹配由数字、小写字母、大写字母组成的字符串,且字符串的长度至少为1个字符
        String pattern = "[0-9a-zA-Z]+";
        if (!userAccount.matches(pattern)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号包含特殊字符");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 用户不存在
        if (user == null) {
            log.info("user login failed, userAccount cannot match userPassword");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        }
        if (user.getStatus().equals(UserAccountStatusEnum.BAN.getValue())) {
            log.info("用户账号已被封禁，userAccount={}", userAccount);
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已被封禁");
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        // 3. 记录用户的登录态
        request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, userVO);
        return userVO;
    }

    /**
     * 获取当前登录用户
     *
     * @param request 要求
     * @return {@link User}
     */
    @Override
    public UserVO getLoginUser(HttpServletRequest request) {
        // 先判断是否已登录
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        UserVO currentUser = (UserVO) userObj;
        if (currentUser == null || currentUser.getId() == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        // 从数据库查询（追求性能的话可以注释，直接走缓存）
        long userId = currentUser.getId();
        User user = this.getById(userId);
        if (user == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        if (user.getStatus().equals(UserAccountStatusEnum.BAN.getValue())) {
            throw new BusinessException(ErrorCode.PROHIBITED, "账号已封禁");
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
        User user = (User) userObj;
        return user != null && UserConstant.ADMIN_ROLE.equals(user.getUserRole());
    }

    /**
     * 是游客
     *
     * @param request
     * @return
     */
    @Override
    public User isTourist(HttpServletRequest request) {
        Object attribute = request.getSession().getAttribute(USER_LOGIN_STATE);
        UserVO userVO = (UserVO) attribute;
        if (userVO == null || userVO.getId() == null) {
            return null;
        }
        return userMapper.selectById(userVO.getId());
    }

    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        if (request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE) == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        // 移除登录态
        request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);
        return true;
    }

    /**
     * 使用邮箱注册
     *
     * @param userEmailRegisterRequest
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public long userEmilRegister(UserEmailRegisterRequest userEmailRegisterRequest) {
        String emailAccount = userEmailRegisterRequest.getEmailAccount();
        String captcha = userEmailRegisterRequest.getCaptcha();
        String userName = userEmailRegisterRequest.getUserName();
        String invitationCode = userEmailRegisterRequest.getInvitationCode();
        if (StringUtils.isAnyBlank(emailAccount, captcha)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
        }
        if (userName.length() > 40) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "昵称过长");
        }
        String emailPattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        if (!emailAccount.matches(emailPattern)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式错误");
        }
        String cacheCaptcha = redisTemplate.opsForValue().get(CAPTCHA_CACHE_KEY + emailAccount);
        if (StringUtils.isBlank(cacheCaptcha)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码已过期");
        }
        //去空格
        captcha = captcha.trim();
        if (!cacheCaptcha.equals(captcha)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误");
        }
        String redissonLock = (EMAIL_REGISTER_LOCK + emailAccount).intern();
        return redissonLockUtil.redissonDistributedLocks(redissonLock, () -> {
            //账号是否已存在
            Long count = userMapper.selectCount(Wrappers.<User>lambdaQuery()
                    .eq(User::getUserAccount, emailAccount));
            if (count > 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号已存在");
            }
            //查询是否是被邀请用户
            User invitedUser = null;
            if (StringUtils.isNotBlank(invitationCode)) {
                invitedUser = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                        .eq(User::getInvitationCode, invitationCode));
                if (invitedUser == null || invitedUser.getIsDelete() == 1) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "邀请码不存在");
                }
            }

            //ak,sk
            String accessKey = DigestUtils.md5DigestAsHex((SALT + emailAccount + VOCHER).getBytes());
            String secretKey = DigestUtils.md5DigestAsHex((SALT + emailAccount + VOCHER + generateRandomString(10)).getBytes());

            //3.插入数据
            User user = new User();
            user.setUserAccount(emailAccount);
            user.setUserName(userName);
            user.setAccessKey(accessKey);
            user.setSecretKey(secretKey);
            if (invitedUser != null) {
                //是邀请用户,钱包余额默认给100
                user.setBalance(100);
                addWalletBalance(invitedUser.getId(), 100);
            } else {
                //不是邀请用户,钱包余额默认给0
                user.setBalance(30);
            }
            //设置邀请码
            user.setInvitationCode(generateRandomString(8));
            int insert = userMapper.insert(user);
            if (insert <= 0) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败");
            }
            return user.getId();
        }, "邮箱账号注册失败");
    }

    /**
     * 使用邮箱登录
     *
     * @param userEmailLoginRequest
     * @param request
     * @return
     */
    @Override
    public UserVO userEmailLogin(UserEmailLoginRequest userEmailLoginRequest, HttpServletRequest request) {
        String emailAccount = userEmailLoginRequest.getEmailAccount();
        String captcha = userEmailLoginRequest.getCaptcha();
        //1.校验
        if (StringUtils.isAnyBlank(emailAccount, captcha)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        String emailPattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        if (!emailAccount.matches(emailPattern)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "邮箱格式错误");
        }
        String cacheCaptha = redisTemplate.opsForValue().get(CAPTCHA_CACHE_KEY + emailAccount);
        if (StringUtils.isBlank(cacheCaptha)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码不存在或已过期");
        }
        captcha = captcha.trim();
        if (!captcha.equalsIgnoreCase(cacheCaptha)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "验证码错误");
        }
        //2.查询用户是否存在
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.eq(User::getUserAccount, emailAccount);
        User user = userMapper.selectOne(userLambdaQueryWrapper);
        if (user == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在");
        }
        if (user.getStatus() == UserAccountStatusEnum.BAN.getValue()) {
            throw new BusinessException(ErrorCode.PROHIBITED, "用户已被封禁");
        }
        UserVO userVO = new UserVO();

        BeanUtils.copyProperties(user, userVO);
        request.getSession().setAttribute(USER_LOGIN_STATE, userVO);

        return userVO;
    }

    /**
     * 绑定邮箱
     * @param userEmailBindRequest
     * @param request
     * @return
     */
    @Override
    public UserVO userBindEmail(UserEmailBindRequest userEmailBindRequest, HttpServletRequest request) {
        String emailAccount = userEmailBindRequest.getEmailAccount();
        String captcha = userEmailBindRequest.getCaptcha();
        //1.校验
        if (StringUtils.isAnyBlank(emailAccount, captcha)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "参数为空");
        }
        String emailPattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        if (!emailAccount.matches(emailPattern)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "邮箱格式错误");
        }
        String cacheCaptha = redisTemplate.opsForValue().get(CAPTCHA_CACHE_KEY + emailAccount);
        if (StringUtils.isBlank(cacheCaptha)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "验证码不存在或已过期");
        }
        captcha = captcha.trim();
        if (!captcha.equalsIgnoreCase(cacheCaptha)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "验证码错误");
        }
        //查看用户是否已经绑定邮箱
        UserVO loginUser = this.getLoginUser(request);
        if (loginUser.getEmail() != null && emailAccount.equals(loginUser.getEmail())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "用户已绑定邮箱");
        }
        //查看邮箱是否已经被绑定
        User user = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getEmail, emailAccount));
        if (user!=null){
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "邮箱已被绑定");
        }
        //2.绑定邮箱
        user=new User();
        user.setId(loginUser.getId());
        user.setEmail(emailAccount);
        int updateById = userMapper.updateById(user);
        if (updateById == 0) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "绑定邮箱失败");
        }
        loginUser.setEmail(emailAccount);
        return loginUser;
    }

    /**
     * 解绑邮箱
     *
     * @param userEmailBindRequest
     * @param request
     * @return
     */
    @Override
    public UserVO userUnBindEmail(UserEmailBindRequest userEmailBindRequest, HttpServletRequest request) {
        String emailAccount = userEmailBindRequest.getEmailAccount();
        String captcha = userEmailBindRequest.getCaptcha();
        //1.校验
        if (StringUtils.isAnyBlank(emailAccount, captcha)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "参数为空");
        }
        String emailPattern = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
        if (!emailAccount.matches(emailPattern)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "邮箱格式错误");
        }
        String cacheCaptha = redisTemplate.opsForValue().get(CAPTCHA_CACHE_KEY + emailAccount);
        if (StringUtils.isBlank(cacheCaptha)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "验证码不存在或已过期");
        }
        captcha = captcha.trim();
        if (!captcha.equalsIgnoreCase(cacheCaptha)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "验证码错误");
        }
        //查询用户是否已经绑定邮箱
        UserVO loginUser = this.getLoginUser(request);
        if (loginUser.getEmail() == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "用户未绑定邮箱");
        }
        if (!loginUser.getEmail().equals(emailAccount)){
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "用户邮箱不匹配");
        }
        //解绑邮箱
        User user = new User();
        user.setId(loginUser.getId());
        user.setEmail("");
        int updateById = userMapper.updateById(user);
        if (updateById == 0) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "解绑邮箱失败");
        }
        loginUser.setEmail(null);
        return loginUser;
    }

    /**
     * 校验
     *
     * @param user
     * @param b
     */
    @Override
    public void validUser(User user, boolean b) {
     if (user==null){
         throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户不存在");
     }
        String userAccount = user.getUserAccount();
        String userPassword = user.getUserPassword();
        Integer balance = user.getBalance();
        //创建是,所有参数必须非空
        if (b){
            if (StringUtils.isAnyBlank(userAccount,userPassword)){
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户名或密码不能为空");
            }
            //添加用户生成的8位邀请码
            user.setInvitationCode(generateRandomString(8));
        }
        //  5. 账户不包含特殊字符
        // 匹配由数字、小写字母、大写字母组成的字符串,且字符串的长度至少为1个字符
        String pattern = "[0-9a-zA-Z]+";
        if (StringUtils.isNotBlank(userAccount) && !userAccount.matches(pattern)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号由数字、小写字母、大写字母组成");
        }
        if (ObjectUtils.isNotEmpty(balance) && balance < 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户余额不能为负数");
        }
        //账户不能重复
        Long count = userMapper.selectCount(Wrappers.<User>lambdaQuery()
                .eq(StringUtils.isNotEmpty(userAccount), User::getUserAccount, userAccount));
        if (count > 0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"账号重复");
        }
    }

    /**
     * 更新凭证
     *
     * @param user
     * @return
     */
    @Override
    public UserVO updateVoucher(User user) {
        String accessKey = DigestUtils.md5DigestAsHex((Arrays.toString(RandomUtil.randomBytes(10)) + SALT + VOCHER).getBytes());
        String secretKey = DigestUtils.md5DigestAsHex((SALT + VOCHER + Arrays.toString(RandomUtil.randomBytes(10))).getBytes());
        user.setAccessKey(accessKey);
        user.setSecretKey(secretKey);
        int update = userMapper.updateById(user);
        if (update == 0) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "更新凭证失败");
        }
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }
    //

    /**
     * 生成随机字符串
     *
     * @param length 长
     * @return {@link String}
     */
    public String generateRandomString(int length) {
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder(length);
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            int index = random.nextInt(characters.length());
            char randomChar = characters.charAt(index);
            sb.append(randomChar);
        }
        return sb.toString();
    }

    /**
     * 用户冲值
     *
     * @param userId
     * @param addPoints
     * @return
     */
    @Override
    public boolean addWalletBalance(Long userId, Integer addPoints) {
        LambdaUpdateWrapper<User> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        userLambdaUpdateWrapper.eq(User::getId, userId);
        userLambdaUpdateWrapper.setSql("balance = balance + " + addPoints);
        return this.update(userLambdaUpdateWrapper);
    }

    /**
     * 减少钱包余额
     *
     * @param userId
     * @param reducePoints
     * @return
     */
    @Override
    public boolean reduceWalletBalance(Long userId, Integer reducePoints) {
        LambdaUpdateWrapper<User> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        userLambdaUpdateWrapper.eq(User::getId, userId);
        userLambdaUpdateWrapper.setSql("balance = balance - " + reducePoints);
        return this.update(userLambdaUpdateWrapper);
    }

}




